package com.compunit.composite.core.xml;

import java.io.IOException;
import java.io.InputStreamReader;
import java.io.StreamTokenizer;
import java.net.URL;

import com.compunit.composite.core.interfaces.xml.IXMLParser;
import com.compunit.composite.core.interfaces.xml.IXMLTag;

/**
 * <code>XMLParser</code> defines the implementation of
 * the utility XML file parser.
 *
 * @author Yi Wang (Neakor)
 * @version Creation date: 11-06-2009 19:24 EST
 * @version Modified date: 11-07-2009 14:36 EST
 */
public class XMLParser implements IXMLParser {
	/**
	 * The root <code>IXMLTag</code>.
	 */
	private IXMLTag root;

	/**
	 * Constructor of <code>XMLParser</code>.
	 */
	public XMLParser() {
		this.root = null;
	}

	@Override
	public void process(final URL file) throws IOException {
		StreamTokenizer parser = new StreamTokenizer(new InputStreamReader(file.openStream()));
		parser.eolIsSignificant(true);
		parser.slashSlashComments(true);
		parser.slashStarComments(true);
		parser.ordinaryChar('<');
		parser.ordinaryChar('>');
		parser.ordinaryChar('/');
		parser.ordinaryChar('=');
		parser.quoteChar('\"');
		this.root = this.nextTag(parser, null);
	}

	private IXMLTag nextTag(final StreamTokenizer parser, final XMLTag parent) throws IOException {
		// Advance to the tag name.
		while(parser.ttype != StreamTokenizer.TT_WORD) parser.nextToken();
		final String name = parser.sval;
		// Create tag.
		final XMLTag tag = new XMLTag(name);
		if(parent != null) parent.addChild(tag);
		// Parse tag.
		boolean hasChildren = true;
		while(parser.nextToken() != '>') {
			final int type = parser.ttype;
			// If a string, may be a key.
			if(type == StreamTokenizer.TT_WORD) {
				final String key = parser.sval;
				// It is a key, add key-value pair.
				if(parser.nextToken() == '=') {
					while(parser.nextToken() != '\"');
					final String value = parser.sval;
					tag.addAttribute(key, value);
				}
			// If it is '/', no children.
			} else if(type == '/') {
				hasChildren = false;
			}
		}
		while(parser.nextToken() != StreamTokenizer.TT_EOL);
		// Parse children recursively.
		if(hasChildren) {
			while(true) {
				this.nextTag(parser, tag);
				while(parser.nextToken() != '<');
				final int type = parser.nextToken();
				if(type == '/') break;
			}
		}
		return tag;
	}

	@Override
	public IXMLTag getRoot() {
		return this.root;
	}

	@Override
	public void cleanup() {
		this.root = null;
	}
}
